﻿/**
 * FFXIIRCGateway
 *
 * @package ffxiircgateway
 * @version $Id: App.cs 23 2009-05-10 02:18:25Z ayunyan $
 * @author ayunyan <ayu@commun.jp>
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
 * @link http://code.google.com/p/ffxiircgateway
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Windows.Forms;
using System.Threading;

namespace FFXIIRCGateway
{
    public class App : ApplicationContext
    {
        private NotifyIcon notifyIcon;
        private ContextMenuStrip notifyMenu;
        private SettingForm settingForm;
        private ConsoleForm consoleForm;
        private IRCClient ircClient;
        private LogReader logReader;
        private TextCommand textCommand;
        private ProcessWatcher processWatcher;

        public App()
        {
            // タスクトレイ
            this.notifyMenu = new ContextMenuStrip();
            ToolStripMenuItem aboutMenuItem = new ToolStripMenuItem();
            aboutMenuItem.Text = "FFXIIRCGatewayについて(&A)";
            aboutMenuItem.Click += new EventHandler(aboutMenuItem_Click);
            this.notifyMenu.Items.Add(aboutMenuItem);
            this.notifyMenu.Items.Add("-");

            ToolStripMenuItem connectMenuItem = new ToolStripMenuItem();
            connectMenuItem.Name = "Connect";
            connectMenuItem.Text = "接続(&C)";
            connectMenuItem.Click += new EventHandler(connectMenuItem_Click);
            this.notifyMenu.Items.Add(connectMenuItem);

            ToolStripMenuItem disconnectMenuItem = new ToolStripMenuItem();
            disconnectMenuItem.Name = "Disconnect";
            disconnectMenuItem.Text = "切断(&D)";
            disconnectMenuItem.Click += new EventHandler(disconnectMenuItem_Click);
            disconnectMenuItem.Enabled = false;
            this.notifyMenu.Items.Add(disconnectMenuItem);

            this.notifyMenu.Items.Add("-");
            this.notifyMenu.Items.Add("設定(&S)", null, new EventHandler(this.notifyMenuItems_Setting));

            ToolStripMenuItem consoleMenuItem = new ToolStripMenuItem();
            consoleMenuItem.Click += new EventHandler(consoleMenuItem_Click);
            consoleMenuItem.Text = "コンソール(&O)";
            this.notifyMenu.Items.Add(consoleMenuItem);

            this.notifyMenu.Items.Add("-");
            this.notifyMenu.Items.Add("終了(&X)", null, new EventHandler(this.notifyMenuItems_Exit));

            this.notifyIcon = new NotifyIcon();
            this.notifyIcon.Icon = global::FFXIIRCGateway.Properties.Resources.fig;
            this.notifyIcon.ContextMenuStrip = this.notifyMenu;
            this.notifyIcon.Visible = true;

            this.ircClient = new IRCClient();
            this.ircClient.Connected += new IRCClient.ConnectedEventHandler(ircClient_Connected);
            this.ircClient.Disconnected += new IRCClient.DisconnectedEventHandler(ircClient_Disconnected);
            this.ircClient.LoginComplete += new IRCClient.LoginCompleteEventHandler(ircClient_LoginComplete);
            this.ircClient.PRIVMSG += new IRCClient.PRIVMSGEventHandler(ircClient_PRIVMSG);
            this.ircClient.NOTICE += new IRCClient.NOTICEEventHandler(ircClient_NOTICE);

            this.consoleForm = new ConsoleForm(this.ircClient);

            this.processWatcher = new ProcessWatcher("pol", "FFXiMain.dll");
            this.processWatcher.ProcessStarted += new ProcessWatcher.ProcessStartedEventHandler(processWatcher_ProcessStarted);
            this.processWatcher.ProcessExited += new ProcessWatcher.ProcessExitedEventHandler(processWatcher_ProcessExited);
            this.processWatcher.ModuleLoaded += new ProcessWatcher.ModuleLoadedEventHandler(processWatcher_ModuleLoaded);
            this.processWatcher.ModuleUnloaded += new ProcessWatcher.ModuleUnloadedEventHandler(processWatcher_ModuleUnloaded);
            this.processWatcher.Start();

            if (Properties.Settings.Default.AutoConnect)
            {
                this.ircClient_Connect();
            }
        }

        void processWatcher_ProcessStarted(Process hProcess)
        {
            this.consoleForm.addLog("PlayOnline Viewerが開始されました。");
        }
        
        void processWatcher_ProcessExited()
        {
            this.consoleForm.addLog("PlayOnline Viewerが終了しました。");
        }

        void processWatcher_ModuleLoaded(Process hProcess, ProcessModule hModule)
        {
            this.consoleForm.addLog("FFXIが開始されました。");
            Memory memory = new Memory(hProcess, hModule);

            this.logReader = new LogReader(memory);
            this.logReader.NewLine += new LogReader.NewLineEventHandler(logReader_NewLine);
            this.logReader.Start();

            this.textCommand = new TextCommand(memory);
        }

        void processWatcher_ModuleUnloaded()
        {
            this.consoleForm.addLog("FFXIが終了しました。");
            this.logReader.Stop();
            this.logReader = null;
            this.textCommand = null;
        }

        private void logReader_NewLine(ChatLine chatLine)
        {
            switch (chatLine.type)
            {
                // Say
                case "01":
                case "09":
                    this.consoleForm.addLog("Say: " + chatLine.text);
                    if (Properties.Settings.Default.RecvChatSay)
                    {
                        this.ircClient.Privmsg(Properties.Settings.Default.Channel, ((char)0x03).ToString() + "00" + chatLine.text);
                    }
                    break;
                // Shout
                case "0a":
                    this.consoleForm.addLog("Shout: " + chatLine.text);
                    break;
                // Tell
                case "04":
                case "0c":
                    this.consoleForm.addLog("Tell: " + chatLine.text);
                    if (Properties.Settings.Default.RecvChatTell)
                    {
                        this.ircClient.Privmsg(Properties.Settings.Default.Channel, ((char)0x03).ToString() + "13" + chatLine.text);
                    }
                    break;
                // Party
                case "05":
                case "0d":
                    this.consoleForm.addLog("Party: " + chatLine.text);
                    if (Properties.Settings.Default.RecvChatParty)
                    {
                        this.ircClient.Privmsg(Properties.Settings.Default.Channel, ((char)0x03).ToString() + "10" + chatLine.text);
                    }
                    break;
                // Linkshell
                case "06":
                case "0e":
                    this.consoleForm.addLog("LS: " + chatLine.text);
                    if (Properties.Settings.Default.RecvChatLS)
                    {
                        this.ircClient.Privmsg(Properties.Settings.Default.Channel, ((char)0x03).ToString() + "09" + chatLine.text);
                    }
                    break;
                // Linkshell Message
                case "cd":
                    this.consoleForm.addLog("LSMessage: " + chatLine.text);
                    break;
            }
        }

        private void ircClient_PRIVMSG(object sender, IRCMessage message)
        {
            if (message.channel.ToLower() != Properties.Settings.Default.Channel.ToLower()) return;
            if (this.textCommand == null) return;

            string[] cmds = message.message.Split(new char[] { ' ' }, 3);
            if (cmds[0].ToLower() == Properties.Settings.Default.CommandPrefix.ToLower())
            {
                if (cmds.Length != 3) return; 
                switch (cmds[1])
                {
                    case "linkshell":
                    case "l":
                        if (Properties.Settings.Default.SendChatLS)
                        {
                            this.textCommand.Send("/l <" + message.nick + "@irc> " + cmds[2]);
                        }
                        break;
                    case "party":
                    case "p":
                        if (Properties.Settings.Default.SendChatParty)
                        {
                            this.textCommand.Send("/p <" + message.nick + "@irc> " + cmds[2]);
                        }
                        break;
                    case "say":
                        if (Properties.Settings.Default.SendChatSay)
                        {
                            this.textCommand.Send("/say <" + message.nick + "@irc> " + cmds[2]);
                        }
                        break;
                    case "tell":
                        string[] args = cmds[2].Split(new char[] { ' ' }, 2);
                        if (args.Length == 2 && Properties.Settings.Default.SendChatTell)
                        {
                            this.textCommand.Send("/tell " + args[0] + " <" + message.nick + "@irc> " + args[1]);
                        }
                        break;
                }
                return;
            }

            // Party
            if (Properties.Settings.Default.DefaultChat == 1 && Properties.Settings.Default.SendChatParty)
            {
                this.textCommand.Send("/p <" + message.nick + "@irc> " + message.message);
            }
            // Say
            else if (Properties.Settings.Default.DefaultChat == 2 && Properties.Settings.Default.SendChatSay)
            {
                this.textCommand.Send("/say <" + message.nick + "@irc> " + message.message);
            }
            // Linkshell
            else if ( Properties.Settings.Default.SendChatLS)
            {
                this.textCommand.Send("/l <" + message.nick + "@irc> " + message.message);
            }
        }

        void ircClient_NOTICE(object sender, IRCMessage message)
        {
            if (message.channel.ToLower() != Properties.Settings.Default.Channel.ToLower()) return;
            if (this.textCommand == null) return;

            if (Properties.Settings.Default.IRCBotNick.Length < 1 ||
                message.nick == null ||
                Properties.Settings.Default.IRCBotNick.ToLower() != message.nick.ToLower()) return;

            // Party
            if (Properties.Settings.Default.DefaultChat == 1 && Properties.Settings.Default.SendChatParty)
            {
                this.textCommand.Send("/p " + message.message);
            }
            // Linkshell
            else if (Properties.Settings.Default.SendChatLS)
            {
                this.textCommand.Send("/l " + message.message);
            }
        }

        private void ircClient_LoginComplete(object sender)
        {
            this.ircClient.Join(Properties.Settings.Default.Channel);
        }

        private delegate void ircClient_DisconnectedDelegate(object sender);
        private void ircClient_Disconnected(object sender)
        {
            // FIXME TODO: AppクラスにISynchronizeInvokeインターフェースを実装する
            if (this.notifyMenu.InvokeRequired) {
                this.notifyMenu.Invoke(new ircClient_DisconnectedDelegate(this.ircClient_Disconnected), new object[] { sender });
                return;
            }
            this.consoleForm.addLog("切断されました。");
            this.notifyIcon.BalloonTipText = "切断されました。";
            this.notifyMenu.Items.Find("Connect", false)[0].Enabled = true;
            this.notifyMenu.Items.Find("Disconnect", false)[0].Enabled = false;
        }

        private delegate void ircClient_ConnectedDelegate(object sender);
        private void ircClient_Connected(object sender)
        {
            // FIXME TODO: AppクラスにISynchronizeInvokeインターフェースを実装する
            if (this.notifyMenu.InvokeRequired)
            {
                this.notifyMenu.Invoke(new ircClient_ConnectedDelegate(this.ircClient_Connected), new object[] { sender });
                return;
            }
            this.consoleForm.addLog("接続しました。");
            this.notifyIcon.BalloonTipText = "接続しました。";
            this.notifyMenu.Items.Find("Connect", false)[0].Enabled = false;
            this.notifyMenu.Items.Find("Disconnect", false)[0].Enabled = true;
        }

        private void ircClient_Connect()
        {
            if (Properties.Settings.Default.Host.Length < 1 ||
                Properties.Settings.Default.NickName.Length < 1 ||
                Properties.Settings.Default.UserName.Length < 1 ||
                Properties.Settings.Default.RealName.Length < 1)
            {
                MessageBox.Show("設定が完了していません。", "FFXIIRCGateway", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                if (this.settingForm == null || this.settingForm.IsDisposed)
                {
                    this.settingForm = new SettingForm();
                }
                this.settingForm.Show();
                this.settingForm.Activate();
                return;
            }
            this.notifyMenu.Items.Find("Connect", false)[0].Enabled = false;
            this.notifyMenu.Items.Find("Disconnect", false)[0].Enabled = false;
            this.consoleForm.addLog(Properties.Settings.Default.Host + ":" +
                Properties.Settings.Default.Port + "に接続しています...");
            this.ircClient.Connect(
                Properties.Settings.Default.Host,
                Properties.Settings.Default.Port,
                Properties.Settings.Default.NickName,
                Properties.Settings.Default.UserName,
                Properties.Settings.Default.RealName);
        }

        private void disconnectMenuItem_Click(object sender, EventArgs e)
        {
            this.notifyMenu.Items.Find("Connect", false)[0].Enabled = false;
            this.notifyMenu.Items.Find("Disconnect", false)[0].Enabled = false;
            this.ircClient.Quit();
        }

        private void connectMenuItem_Click(object sender, EventArgs e)
        {
            this.ircClient_Connect();
        }

        private void notifyMenuItems_Exit(object sender, EventArgs e)
        {
            // ProcessWatcherを停止
            this.processWatcher.Stop();
            // ログリーダーを停止
            if (this.logReader != null)
            {
                this.logReader.Stop();
            }
            // IRCに接続中の場合は切断
            if (this.ircClient.isConnected())
            {
                this.ircClient.Quit();
                // 切断完了まで待機
                while (this.ircClient.isConnected())
                {
                    Thread.Sleep(100);
                }
            }
            Application.Exit();
        }

        private void notifyMenuItems_Setting(object sender, EventArgs e)
        {
            if (this.settingForm == null || this.settingForm.IsDisposed)
            {
                this.settingForm = new SettingForm();
            }
            this.settingForm.Show();
            this.settingForm.Activate();
        }

        private void consoleMenuItem_Click(object sender, EventArgs e)
        {
            this.consoleForm.Show();
            this.consoleForm.Activate();
        }

        private void aboutMenuItem_Click(object sender, EventArgs e)
        {
            AboutForm aboutForm = new AboutForm();
            aboutForm.ShowDialog();
        }

        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            App app = new App();
            Application.Run(app);
        }
    }
}
